/*
 * Matt Adams
 * TCSS 558 HW2
 * Fall 2013
 */
package server;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.Socket;
import java.net.SocketException;
import java.util.SortedSet;
import java.util.StringTokenizer;

import tools.Log;

/**
 * Runnable called by ConcurrentTCPSpellingServer.
 * 
 * @author Matt Adams
 */
public class ConcurrentTCPServerRunnable implements Runnable {
    //Static fields
    private static final String OK = "OK";
    private static final String NO = "NO";
    private static final String INVALID = "INVALID";
    private static final String BYE = "GOODBYE";
    
    /**
     * Socket from client.
     */
    private final Socket clientSocket;
    
    /**
     * ConcurrentTCPSpellingServer object.
     */
    private final ConcurrentTCPSpellingServer tcpServer;
    
    /**
     * Number of initialized sessions.
     */
    private final int numInitializedSessions;

    /**
     * Constructs ConcurrentTCPServerRunnable object.
     * 
     * @param clientSocket Socket from client.
     * @param tcpServer ConcurrentTCPSpellingServer object.
     * @param numInitializedSessions Number of initialized sessions.
     */
    public ConcurrentTCPServerRunnable(final Socket clientSocket, final ConcurrentTCPSpellingServer tcpServer, final int numInitializedSessions) {
        this.clientSocket = clientSocket;
        this.tcpServer = tcpServer;
        this.numInitializedSessions = numInitializedSessions;        
    }

    @Override
    public final void run() {
        String endSession = null;
        while (endSession != BYE) {
            try {
                final BufferedReader bufIn = new BufferedReader(new InputStreamReader(this.clientSocket.getInputStream()));
                final BufferedWriter bufOut = new BufferedWriter(new OutputStreamWriter(this.clientSocket.getOutputStream()));
                String queryLine = null;
                while (queryLine == null) {
                    queryLine = bufIn.readLine();
                }
                if (queryLine.isEmpty()) {
                    bufOut.write(BYE + "\r\n");
                    bufOut.flush();
                    Log.out(this.numInitializedSessions + " - Queries complete. Closing local client socket.");
                    this.clientSocket.close();
                    endSession = BYE;
                } else {
                    final StringTokenizer  tokenizer = new StringTokenizer(queryLine);
                    if (tokenizer.countTokens() != 2) {
                        Log.err(this.numInitializedSessions + " - Malformed query, has incorrect number of tokens: " + tokenizer.countTokens());
                        sendInvalidResponse(bufOut);
                        return;
                    }
                    final int queryNumber = parseQueryNumber(tokenizer.nextToken(), bufOut);
                    if (queryNumber < 0) {
                        return;
                    }
                    final String word = tokenizer.nextToken();

                    int numberOfWords = 0;
                    SortedSet<String> closeWords = null;
                    if (this.tcpServer.wordList.isInList(word)) {
                        numberOfWords = -1;
                    } else {
                        closeWords = this.tcpServer.wordList.getCloseWords(word);
                        numberOfWords = closeWords.size();
                    }
                    final String closeWordString = this.tcpServer.displayQueryResponse(word, numberOfWords, closeWords, this.numInitializedSessions);

                    if (closeWordString != null) {
                        bufOut.write(queryNumber + " " + NO + " " + closeWordString + "\r\n");
                    } else if (numberOfWords == -1) {
                        bufOut.write(queryNumber + " " + OK + "\r\n");
                    } else {
                        bufOut.write(queryNumber + " " + NO + "\r\n");
                    }
                    bufOut.flush();
                }
            } catch (SocketException e) {
                    Log.err(this.numInitializedSessions + " - Socket exception while running server: " + e);
                    this.tcpServer.errorClose();
            } catch (IOException e) {
                Log.err(this.numInitializedSessions + " - IO problem during server runnable handling receiving and sending data: " + e);
            }
        }
        
        
    }
    
    /**
     * Sends invalid response to client.
     */
    private void sendInvalidResponse(final BufferedWriter bufOut) {
        Log.out(this.numInitializedSessions + " - Connection closed.");
        try {
            bufOut.write(INVALID + "\r\n");
            bufOut.flush();
        } catch (IOException e) {
            Log.err(this.numInitializedSessions + " - Error sending INVALID respone: " + e);
        }
    }
    
    /**
     * Returns integer from string.
     * 
     * @param queryNumber String representation of a number.
     * @return Return integer from string.
     */
    private int parseQueryNumber(final String queryNumber, final BufferedWriter bufOut) {
        int result = 0;
        try {
            result = Integer.parseInt(queryNumber);
            if (result < 0) {
                Log.err(this.numInitializedSessions + " - Malformed query, number cannot be less than 0: " + queryNumber);
                sendInvalidResponse(bufOut);
            }
        } catch(NumberFormatException e) {
            Log.err(this.numInitializedSessions + " - Malformed query, not valid integer before query word");
            result = -1;
            sendInvalidResponse(bufOut);
        }

        return result;
    }

}
